package com.ydkfjs.service.impl;

import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ydkfjs.constants.CommonConstants;
import com.ydkfjs.constants.HttpStatus;
import com.ydkfjs.constants.LoginConstant;
import com.ydkfjs.exception.BusinessException;
import com.ydkfjs.mapper.UserMapper;
import com.ydkfjs.model.dto.LoginRequest;
import com.ydkfjs.model.po.User;
import com.ydkfjs.service.LoginService;
import com.ydkfjs.utils.HBAssert;
import com.ydkfjs.utils.RedisCacheUtils;
import com.ydkfjs.utils.IpUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @Author: hb
 * @CreateTime: 2024-04-02 11:22
 * @Version: 1.0
 */
@Slf4j
@Service
public class LoginServiceImpl implements LoginService {

    private final UserMapper userMapper;
    private final RedisCacheUtils redisCacheUtils;
    private final IpUtils ipUtils;

    public LoginServiceImpl(UserMapper userMapper, RedisCacheUtils redisCacheUtils, IpUtils ipUtils) {
        this.userMapper = userMapper;
        this.redisCacheUtils = redisCacheUtils;
        this.ipUtils = ipUtils;
    }


    /**
     * 登录
     *
     * @param loginRequest
     * @param request
     * @return
     */
    @Override
    public Map<String, String> login(LoginRequest loginRequest, HttpServletRequest request) {

        // 判断用户是否存在
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<User>()
                .eq(User::getUsername, loginRequest.getUsername());

        Long count = userMapper.selectCount(lambdaQueryWrapper);

        HBAssert.isFalse(count <= 0, "用户不存在");

        User user = userMapper.selectOne(lambdaQueryWrapper);

        // 数据库查询的密码
        String passwordDb = user.getPassword();
        String password = loginRequest.getPassword();

        // 获取用户ip, 更新ip
        String ipAddr = ipUtils.getIpAddr(request);
        ipUtils.refreshIpDetailAsync(user.getId(), ipAddr, LoginConstant.USER_LOGIN);

        // 检测用户登录密码次数
        checkLogin(loginRequest.getUsername(), ipAddr, BCrypt.checkpw(password, passwordDb));

        // 用户登录
        StpUtil.login(user.getId());

        // 获取token
        String token = StpUtil.getTokenInfo().getTokenValue();

        Map<String, String> ajax = new HashMap<String, String>();
        ajax.put(CommonConstants.TOKEN, token);
        ajax.put("user", user.toString());

        log.info("用户 {} 登录成功!", user.getId());

        return ajax;
    }

    /**
     * 登录校验失败次数
     */
    private void checkLogin(String phone, String remoteAddr, Boolean supply) {

        // key
        String errorKey = LoginConstant.PWD_ERR_CNT_KEY + phone + ":" + remoteAddr;

        // 密码正确
        if (supply) {
            // 登录成功，清除次数
            redisCacheUtils.deleteObject(errorKey);
            return;
        }

        // 获取用户登录错误次数
        Integer errorNumber = redisCacheUtils.getCacheObject(errorKey);

        // 锁定时间内登录,不允许登录
        if (!ObjectUtil.isNull(errorNumber) && errorNumber.equals(LoginConstant.MAX_ERR_COUNT)) {
            redisCacheUtils.setCacheObject(errorKey, errorNumber, LoginConstant.LOCK_TIME, TimeUnit.MINUTES);
            throw new BusinessException(HttpStatus.ERROR, String.format("密码错误次数 %d 次，请稍后再尝试", LoginConstant.MAX_ERR_COUNT));
        }

        // 密码不正确，判断是否第一次
        errorNumber = ObjectUtil.isNull(errorNumber) ? 1 : errorNumber + 1;
        // 达到规定错误次数 则锁定登录
        if (errorNumber.equals(LoginConstant.MAX_ERR_COUNT)) {
            redisCacheUtils.setCacheObject(errorKey, errorNumber, LoginConstant.LOCK_TIME, TimeUnit.MINUTES);
            throw new BusinessException(HttpStatus.ERROR, String.format("密码错误次数 %d 次，请稍后再尝试", LoginConstant.MAX_ERR_COUNT));
        } else {
            // 未达到规定错误次数 则递增
            redisCacheUtils.setCacheObject(errorKey, errorNumber, LoginConstant.LOCK_TIME, TimeUnit.MINUTES);
            throw new BusinessException(HttpStatus.ERROR, String.format("密码错误！错误次数：%d", errorNumber));
        }
    }
}
